package reservator.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.LinkedHashSet;
import java.util.Iterator;


import reservator.common.Client;
import reservator.common.Journey;
import reservator.common.Reservation;
import reservator.common.Transport;

/**
 * Singleton that represent the server database
 * @author Pascal Cuisinaud, Thomas Di'Meco
 */
public class Database
{
    private final String dbFilename = "reservator.db";
    private final String filePath = "./fichier.txt";

    
    private static Database instance;
    
    private LinkedHashSet<Client>      clients;
    private LinkedHashSet<Journey>     journeys;
    private LinkedHashSet<Reservation> reservations;
    private LinkedHashSet<Transport>   transports;
    
    /**
     * Private constructor (singleton pattern)
     */
    private Database ()
    {
        clients      = new LinkedHashSet<Client>();
        journeys     = new LinkedHashSet<Journey>();
        reservations = new LinkedHashSet<Reservation>();
        transports   = new LinkedHashSet<Transport>();
    }
    
    /**
     * Get the database instance (singleton pattern)
     */
    public static Database getInstance ()
    {
        if (instance == null) instance = new Database();
        return instance;
    }
    
    // *** Add methods which add a new instance of the class corresponding to the HashSet ***
    public boolean addClient (Client client)
    {
        return clients.add(client);
    }
    
    public boolean addJourney (Journey journey)
    {
        return journeys.add(journey);
    }
    
    public boolean addReservation (Reservation reservation)
    {
        return reservations.add(reservation);
    }
    
    public boolean addTransport (Transport transport)
    {
        return transports.add(transport);
    }
    // ***************************************************************************************
    
    /**
     * Search method for client
     * @param Client which have wanted information
     * @return LinkedHashSet<Client> which contains clients who have the wanted informations
     */
    public LinkedHashSet<Client> searchClient (Client target)
    {
    	// Create LinkedHashSet that will be returned
        LinkedHashSet<Client> result = new LinkedHashSet<Client>();
        // Create iterator of the database's LinkedHashSet
        Iterator<Client> itr = clients.iterator();
        //Iterate
        while (itr.hasNext())
        {
        	//Take the current client thanks to the iterator
            Client current = (Client)itr.next();
            //Check if the information of this client suit 
            if (target.getId() != 0 && target.getId() != current.getId() ||
                target.getFirstName() != null && !target.getFirstName().equals(current.getFirstName()) ||
                target.getLastName() != null && !target.getLastName().equals(current.getLastName()))
            	// If no, check the next client
                continue;
            // If yes, add client in the result set
            result.add(current);
        }
        // return the result set
        return result; 
    }

    /**
     * Search method for transport
     * @param Transport which have wanted information
     * @return LinkedHashSet<Transport> which contains transport who have the wanted informations
     */
    public LinkedHashSet<Transport> searchTransport (Transport target)
    {
    	// Create LinkedHashSet that will be returned
        LinkedHashSet<Transport> result = new LinkedHashSet<Transport>();
        // Create iterator of the database's LinkedHashSet
        Iterator<Transport> itr = transports.iterator();
        //Iterate
        while (itr.hasNext())
        {
        	//Take the current transport thanks to the iterator
            Transport current = (Transport)itr.next();
            //Check if the information of this transport suit 
            if (target.getId() != 0 && target.getId() != current.getId() ||
                target.getName() != null && !target.getName().equals(current.getName()) ||
                target.getType() != null && !target.getType().equals(current.getType()) ||
                target.getCapacity() != 0 && target.getCapacity() != current.getCapacity())
            	// If no, check the next transport
                continue;
            // If yes, add transport in the result set
            result.add(current);
        }
        // return the result set
        return result; 
    }

    /**
     * Search method for reservation
     * @param Reservation which have wanted information
     * @return LinkedHashSet<Reservation> which contains reservations who have the wanted informations
     */
    public LinkedHashSet<Reservation> searchReservation (Reservation target)
    {
    	// Create LinkedHashSet that will be returned
        LinkedHashSet<Reservation> result = new LinkedHashSet<Reservation>();
        // Create iterator of the database's LinkedHashSet
        Iterator<Reservation> itr = reservations.iterator();
        //Iterate
        while (itr.hasNext())
        {
        	//Take the current reservation thanks to the iterator
            Reservation current = (Reservation)itr.next();
           //Check if the information of this reservation suit 
            if (target.getId() != 0 && target.getId() != current.getId() ||
                target.getClient() != null && !target.getClient().equals(current.getClient()) ||
                target.getJourney() != null && !target.getJourney().equals(current.getJourney()))
            	// If no, check the next reservation
                continue;
            // If yes, add reservation in the result set
            result.add(current);
        }
        // return the result set
        return result;
    }

    /**
     * Search method for journey
     * @param Journey which have wanted information
     * @return LinkedHashSet<Journey> which contains journeys who have the wanted informations
     */
    public LinkedHashSet<Journey> searchJourney (Journey target)
    {
    	// Create LinkedHashSet that will be returned
        LinkedHashSet<Journey> result = new LinkedHashSet<Journey>();
        // Create iterator of the database's LinkedHashSet
        Iterator<Journey> itr = journeys.iterator();
        //Iterate
        while (itr.hasNext())
        {
        	//Take the current journey thanks to the iterator
            Journey current = (Journey)itr.next();
            //Check if the information of this journey suit 
            if (target.getId() != 0 && target.getId() != current.getId() ||
                target.getStartPlace() != null && !target.getStartPlace().equals(current.getStartPlace()) ||
                target.getEndPlace() != null && !target.getEndPlace().equals(current.getEndPlace()) ||
                target.getStartDate() != null && !target.getStartDate().equals(current.getStartDate()) ||
                target.getEndDate() != null && !target.getEndDate().equals(current.getEndDate()) ||
                target.getTransport() != null && !target.getTransport().equals(current.getTransport()))
            	// If no, check the next journey
                continue;
            // If yes, add journey in the result set
            result.add(current);
        }
        // return the result set
        return result;
    }
    
    /**
     * @return The next available transport id
     */
    public int getNextTransportId ()
    {
        int id = 0;
        
        for (Transport t : transports)
            if (t.getId() > id)
                id = t.getId();
                
        return id+1;
    }
    
    /**
     * @return The next available journey id
     */
    public int getNextJourneyId ()
    {
        int id = 0;
        
        for (Journey j : journeys)
            if (j.getId() > id)
                id = j.getId();
                
        return id+1;
    }
    
    /**
     * @return The next available reservation id
     */
    public int getNextReservationId ()
    {
        int id = 0;
        
        for (Reservation r : reservations)
            if (r.getId() > id)
                id = r.getId();
                
        return id+1;
    }
    
    /**
     * @return The next available client id
     */
    public int getNextClientId ()
    {
        int id = 0;
        
        for (Client c : clients)
            if (c.getId() > id)
                id = c.getId();
                
        return id+1;
    }
    
    /**
     * Commit the data of the database into the db file
     * @return True if the data is well committed, false otherwise
     */
    public boolean commit ()
    {
        try
        {
            FileOutputStream fos = new FileOutputStream(dbFilename, false);
            writeDataUsingSerialization(fos); 
            fos.close();
        }
        catch (IOException exc)
        {
            exc.printStackTrace();
            return false;
        }
        
        return true;
    }
    
    /**
     * Revert the data of the database from the db file
     * @return True if the data is well reverted, false otherwise
     */
    public boolean revert ()
    {
        try
        {
            FileInputStream fis = new FileInputStream(dbFilename);
            readDataUsingSerialization(fis); 
            fis.close();
        }
        catch (FileNotFoundException exc) {}
        catch (IOException exc)
        {
            exc.printStackTrace();
            return false;
        }
        
        return true;
    }
    
    /**
     * Clear the database and erase storage file
     */
    public void clear ()
    {
        // Erase local data
        clients.clear();
        journeys.clear();
        reservations.clear();
        transports.clear();
        
        // Remove database file
        File file = new File(dbFilename);
        file.delete();
    }
    
    /**
     * Method for writing the data of the database in the db file
     */
    private void writeDataUsingSerialization (OutputStream os) throws IOException
    {
        ObjectOutputStream oos = new ObjectOutputStream(os);
        
        oos.writeObject(clients);
        oos.writeObject(journeys);
        oos.writeObject(reservations);
        oos.writeObject(transports);
        
        oos.flush();
        oos.close();
    }
    
    /**
     * Method for reading the data of the database from the db file
     */
    @SuppressWarnings("unchecked")
    private void readDataUsingSerialization (InputStream is) throws IOException
    {
        ObjectInputStream ois = new ObjectInputStream(is);
        
        try
        {
            clients      = (LinkedHashSet<Client>)      ois.readObject();
            journeys     = (LinkedHashSet<Journey>)     ois.readObject();
            reservations = (LinkedHashSet<Reservation>) ois.readObject();
            transports   = (LinkedHashSet<Transport>)   ois.readObject();
        }
        catch (ClassNotFoundException exc)
        {
            exc.printStackTrace();
            throw new IOException("Class not found");
        }
        
        ois.close();
    }

    /**
     * Method for writing the data of the database in the txt file
     */
    public void writeDataInFile () throws IOException
    {

        FileWriter writer = null;
        // prepare Strings that will be written
        String dataClients = clients.toString() + "\n";
        String dataTransport = transports.toString() + "\n";
        String dataJourney = journeys.toString() + "\n";
        String dataReservation = reservations.toString() + "\n";

        try
        {
            // Write above strings in the txt file
            writer = new FileWriter("fichier.txt", false);
            writer.write(dataClients, 0, dataClients.length());
            writer.write(dataTransport, 0, dataTransport.length());
            writer.write(dataJourney, 0, dataJourney.length());
            writer.write(dataReservation, 0, dataReservation.length());
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        } finally
        {
            if (writer != null)
                writer.close();
        }
    }

    /**
     * Method for reading the data of the database from the txt file
     */
    public void readDataInFile () throws IOException
    {
        // create temporarily instance of classes
        Client clientcurrent = new Client();
        Transport transportcurrent = new Transport();
        Journey journeycurrent = new Journey();
        Reservation reservationcurrent = new Reservation();

        try
        {
            BufferedReader buff = new BufferedReader(new FileReader(filePath));

            try
            {
                // read the first line : Client line
                String line;
                line = buff.readLine();
                // Split the line into separated clients
                String client[] = line.split("Client");
                // add each client to the database
                for (int i = 1; i < client.length; i++)
                {
                    clientcurrent.createClient(client[i]);
                    this.addClient(clientcurrent);
                }
                // read the next line : Transport line
                line = buff.readLine();
                // Split the line into separated transports
                String transport[] = line.split("Transport");
                // add each transport to the database
                for (int i = 1; i < transport.length; i++)
                {
                    transportcurrent.createTransport(transport[i]);
                    this.addTransport(transportcurrent);
                }
                // read the next line : Journey line
                line = buff.readLine();
                // Split the line into separated journeys
                String journey[] = line.split("Journey");
                // add each journey to the database
                for (int i = 1; i < journey.length; i++)
                {
                    journeycurrent.createJourney(journey[i]);
                    this.addJourney(journeycurrent);
                }
                // read the next line : Reservation line
                line = buff.readLine();
                // Split the line into separated reservations
                String reservation[] = line.split("Reservation");
                // add each reservation to the database
                for (int i = 1; i < reservation.length; i++)
                {
                    reservationcurrent.createReservation(reservation[i]);
                    this.addReservation(reservationcurrent);
                }
            } finally
            {
                buff.close();
            }
        }
        catch (IOException ioe)
        {
            System.out.println("Erreur --" + ioe.toString());
        }
    }
}
